package robocup.component.coach;

import java.util.ArrayList;
import java.util.List;

import robocup.component.ControllerNumbers;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.CoachVisualInfo;
import robocup.component.infotypes.InfoReceiver;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.speechacts.coachlanguage.ActionElement;
import robocup.component.speechacts.coachlanguage.CPoint;
import robocup.component.speechacts.coachlanguage.DefineDirective;
import robocup.component.speechacts.coachlanguage.DefineMessage;
import robocup.component.speechacts.coachlanguage.Directive;
import robocup.component.speechacts.coachlanguage.Region;
import robocup.component.speechacts.coachlanguage.TypeConstants;
import robocup.component.speechacts.coachlanguage.UnumSet;
import robocup.component.worldobjects.Player;

public class OpponentFormationAnalyzer
    implements InfoReceiver {

  // CoachWorld
  private CoachWorld         cwm;

  private OLCoachTactic      olct;

  // CoachProphet
  private CoachProphet       cp;

  private LinearRegression   lg_x[];         // = new LinearRegression(3000);
  private LinearRegression   lg_y[];         // = new LinearRegression(3000);

  /*
   * private LinearRegression lr_x_off[]; private LinearRegression lr_y_off[];
   * 
   * private LinearRegression lr_x_def[]; ; private LinearRegression lr_y_def[];
   */

  private List<Vektor>       home;
  private List<Vektor>       attraction;

  /*
   * private List<Vektor> home_off; private List<Vektor> attraction_off;
   * 
   * private List<Vektor> home_def; private List<Vektor> attraction_def;
   */

  private double             b_x;
  private double             b_y;
  private double             p_x;
  private double             p_y;

  private double             ball_x;
  private double             ball_y;

  private double             calc_length;    // after calc_length steps:
                                              // compute new values
  private double             epsilon_home;   // maximum offset between old and
                                              // new value
  private double             epsilon_attr;   // maximum offset between old and
                                              // new value
  private double             updatePositions; // steps to store Positions
  private double             maxError;       // maximum allowed mean error

  private Vektor             tmp;
  private Player             closest;

  boolean                    defense;
  boolean                    sended;         // true if positions has been
                                              // sended to players
  private List<List<Vektor>> positions;

  public OpponentFormationAnalyzer(
      final OLCoachTactic ol) {

    this.olct = ol;
    this.cwm = ol.getCwm();
    this.cp = ol.getCoachProphet();

    this.lg_x = new LinearRegression[11];
    this.lg_y = new LinearRegression[11];

    /*
     * this.lr_x_off = new LinearRegression[11]; this.lr_y_off = new
     * LinearRegression[11];
     * 
     * this.lr_x_def = new LinearRegression[11]; this.lr_y_def = new
     * LinearRegression[11];
     */

    for (int i = 0; i < 11; i++) {
      this.lg_x[i] = new LinearRegression(6000);
      this.lg_y[i] = new LinearRegression(6000);

      /*
       * lr_x_off[i] = new LinearRegression(6000); lr_y_off[i] = new
       * LinearRegression(6000);
       * 
       * lr_x_def[i] = new LinearRegression(6000); lr_y_def[i] = new
       * LinearRegression(6000);
       */
    }

    this.home = new ArrayList<Vektor>(11);
    this.attraction = new ArrayList<Vektor>(11);

    this.positions = new ArrayList<List<Vektor>>(600);

    /*
     * this.home_off = new ArrayList<Vektor>(11); this.attraction_off = new
     * ArrayList<Vektor>(11);
     * 
     * this.home_def = new ArrayList<Vektor>(11); this.attraction_def = new
     * ArrayList<Vektor>(11);
     */

    for (int i = 0; i < 11; i++) {

      this.home.add(new Vektor(0, 0));
      this.attraction.add(new Vektor(0, 0));

      /*
       * this.home_off.add(new Vektor(0,0)); this.attraction_off.add(new
       * Vektor(0,0));
       * 
       * this.home_def.add(new Vektor(0,0)); this.attraction_def.add(new
       * Vektor(0,0));
       */
    }

    this.b_x = 0;
    this.b_y = 0;
    this.p_x = 0;
    this.p_y = 0;

    this.ball_x = 0;
    this.ball_y = 0;

    this.calc_length = 300; // **************************** TEST AND CHANGE
    this.epsilon_home = 2; // **************************** TEST AND CHANGE
    this.epsilon_attr = 0.4; // **************************** TEST AND CHANGE
    this.updatePositions = 51;
    this.maxError = 12;

    this.sended = false;

    this.tmp = new Vektor(0, 0);

  }

  public void receivedPlayerTypeInf(final PlayerTypesInfo info) {

    // TODO Auto-generated method stub

  }

  public void receivedAuralInfo(final AuralInfo info) {

    // TODO Auto-generated method stub

  }

  public void update(final CoachVisualInfo info) {

    // report new positions to the LinearRegression-class
    if ((this.cwm.getPlayMode() == ControllerNumbers.PLAY_MODE.PLAY_ON) /*
                                                                         * &&
                                                                         * (info.getCycle() <=
                                                                         * 3000)
                                                                         */) {

      this.ball_x = info.getCoachBall().getXPos();
      this.ball_y = info.getCoachBall().getYPos();

      this.closest = this.cp.getFastestOpponentToBall();

      // Defense == opponent player can reach the ball faster than our players
      // defense =
      // (cp.getFastestFriendToBall().getDistance(cwm.getBallPosition()) <
      // closest.getDistance(cwm.getBallPosition()));

      for (int i = 0; i < 11; i++) {

        // exclude closest player to the ball from calculation
        if (this.closest.getNumber() != (i + 1)) {
          this.b_x = this.ball_x - info.getOtherPlayers()[i].getXPos();
          this.b_y = this.ball_y - info.getOtherPlayers()[i].getYPos();

          this.p_x = info.getOtherPlayers()[i].getXPos();
          this.p_y = info.getOtherPlayers()[i].getYPos();

          this.lg_x[i].addPair(this.b_x, this.p_x);
          this.lg_y[i].addPair(this.b_y, this.p_y);

          // Defense == opponent player can reach the ball faster than our
          // players
          /*
           * if(defense) { lr_x_def[i].addPair(b_x,p_x);
           * lr_y_def[i].addPair(b_y,p_y); } else {
           * lr_x_off[i].addPair(b_x,p_x); lr_y_off[i].addPair(b_y,p_y);
           *  } // else
           */

        } // if

      } // for

    } // if

    // check out if a position has significantly changed
    // if((info.getCycle() + 2) % calc_length == 0) {
    if (info.getCycle() % this.updatePositions == 1) {

      // System.out.println(this.cwm.getVisualCycle() + ". Coach is broadcasting
      // opponent player formation guesses...");

      // store actual positions
      this.addPositions();

      for (int i = 0; i < 11; i++) {
        this.home.get(i).x = this.lg_x[i].getA();
        this.home.get(i).y = this.lg_y[i].getA();

        this.attraction.get(i).x = this.lg_x[i].getB();
        this.attraction.get(i).y = this.lg_y[i].getB();
      }
    }

    // if the qulity is good: data can be sent to players
    if ((info.getCycle() >= 3000) && (this.calcErrors() < this.maxError) && (!this.sended)) {
      System.out
          .println(this.cwm.getVisualCycle() + " Coach will send positions from now. mean error: " + this
              .calcErrors());
      for (int i = 0; i < 11; i++) {
        this.sendRegion(i + 1);
      }
      this.sended = true;
    }

    if (this.sended && (info.getCycle() % this.calc_length == 1)) {

      System.out
          .println(this.cwm.getVisualCycle() + ". Coach is broadcasting opponent player formation guesses........................");

      // toggle through opponents
      for (int i = 0; i < 11; i++) {

        // if the deviance in any direction is larger than "epsilon"
        // store new values and send them to own players
        if ((Math.abs(this.lg_x[i].getA() - this.home.get(i).x) > this.epsilon_home) || (Math
            .abs(this.lg_y[i].getA() - this.home.get(i).y) > this.epsilon_home) || (Math
            .abs(this.lg_x[i].getB() - this.attraction.get(i).x) > this.epsilon_attr) || (Math
            .abs(this.lg_y[i].getB() - this.attraction.get(i).y) > this.epsilon_attr)) {

          // System.out.println(info.getCycle() + ": Player " + (i+1) + " old:
          // pos = (" + home.get(i).x + " , " + home.get(i).y + ") + ball * (" +
          // attraction.get(i).x + " , " + attraction.get(i).y + ")");

          this.home.get(i).x = this.lg_x[i].getA();
          this.home.get(i).y = this.lg_y[i].getA();

          this.attraction.get(i).x = this.lg_x[i].getB();
          this.attraction.get(i).y = this.lg_y[i].getB();

          /*
           * if(defense) {
           * 
           * home_def.get(i).x = lr_x_def[i].getA(); home_def.get(i).y =
           * lr_y_def[i].getA();
           * 
           * attraction_def.get(i).x = lr_x_def[i].getB();
           * attraction_def.get(i).y = lr_y_def[i].getB();
           *  } else {
           * 
           * home_off.get(i).x = lr_x_off[i].getA(); home_off.get(i).y =
           * lr_y_off[i].getA();
           * 
           * attraction_off.get(i).x = lr_x_off[i].getB();
           * attraction_off.get(i).y = lr_y_off[i].getB(); }
           */

          // System.out.println(info.getCycle() + ": Player " + (i+1) + " new:
          // pos = (" + getHome(i+1).x + " , " + getHome(i+1).y + ") + ball * ("
          // + getAttraction(i+1).x + " , " + getAttraction(i+1).y + ")");
          // System.out.println();
          // send positions from player i+1 to friends
          this.sendRegion(i + 1);
        } // if

        // clear the lists

        // lg_x[i].reset();
        // lg_y[i].reset();

        /*
         * lr_x_off[i].reset(); lr_y_off[i].reset();
         * 
         * lr_x_def[i].reset(); lr_y_def[i].reset();
         */
      } // for
    } // if

    // print results
    // if(info.getCycle()% 40 == 1) {

    /*
     * for(int i=0; i<11; i++) {
     * 
     * System.out.println(info.getCycle()); // + ": Player " + (i+1) + ": pos = (" +
     * getHome(i+1).x + " , " + getHome(i+1).y + ") + ball * (" +
     * getAttraction(i+1).x + " , " + getAttraction(i+1).y + ") "); tmp.x =
     * home.get(i).x +attraction.get(i).x * (ball_x - home.get(i).x); tmp.y =
     * home.get(i).y +attraction.get(i).y * (ball_y - home.get(i).y);
     * 
     * 
     * 
     * //System.out.println((i+1) + ":\t diff: " +
     * tmp.sub(cwm.getOpponentPosition(i+1)).getLength());
     * //System.out.println("calc: " + tmp); //System.out.println("real: " +
     * cwm.getPlayerPosition(i+1,false)); } // for
     */

    // System.out.println(info.getCycle());
    // addPositions();
    // calcErrors();
    // System.out.println();
    // } // if

    /*
     * if(info.getCycle() % 51 == 0) {
     * 
     * System.out.println(info.getCycle()); for(int i=0; i<11; i++) {
     * 
     * tmp.x = home.get(i).x +attraction.get(i).x * (ball_x - home.get(i).x);
     * tmp.y = home.get(i).y +attraction.get(i).y * (ball_y - home.get(i).y);
     * 
     * System.out.print((i+1) + ":\t old: " +
     * tmp.sub(cwm.getOpponentPosition(i+1)).getLength());
     * 
     * if(defense) { tmp.x = home_def.get(i).x +attraction_def.get(i).x *
     * (ball_x - home_def.get(i).x); tmp.y = home_def.get(i).y
     * +attraction_def.get(i).y * (ball_y - home_def.get(i).y); } else { tmp.x =
     * home_off.get(i).x +attraction_off.get(i).x * (ball_x -
     * home_off.get(i).x); tmp.y = home_off.get(i).y +attraction_off.get(i).y *
     * (ball_y - home_off.get(i).y); }
     * 
     * System.out.println("\t new: " +
     * tmp.sub(cwm.getOpponentPosition(i+1)).getLength()); } // for
     * System.out.println(); } // if
     */

  } // method receivedVisualInfo

  /**
   * pos = home + attraction * ball;
   * 
   * @param i
   *          number of the player
   * @return a reference to the home position
   */
  public Vektor getHome(final int i) {

    if ((i >= 1) && i <= 11) {
      this.tmp.x = this.lg_x[i - 1].getA();
      this.tmp.y = this.lg_y[i - 1].getA();
    }
    else {
      this.tmp.x = 0;
      this.tmp.y = 0;
    }

    return this.tmp;
  } // method getHome

  /**
   * pos = home + attraction * ball;
   * 
   * @param i
   *          number of the player
   * @return a reference to the ball-attraction-vektor
   */
  public Vektor getAttraction(final int i) {

    if ((i >= 1) && i <= 11) {
      this.tmp.x = this.lg_x[i - 1].getB();
      this.tmp.y = this.lg_y[i - 1].getB();
    }
    else {
      this.tmp.x = 0;
      this.tmp.y = 0;
    }

    return this.tmp;
  } // method getAttraction

  /**
   * sends home and attraction of all opponents to own players
   * 
   */
  public void sendAllRegions() {

    for (int i = 0; i < 11; i++) {
      this.sendRegion(i + 1);
    }
  }

  /**
   * sends home and attraction of opponent "number" to all own players
   * 
   * @param number
   */
  public void sendRegion(final int number) {

    final DefineDirective dd = new DefineDirective();
    final Directive dir = new Directive();
    final UnumSet us = new UnumSet();
    final DefineMessage dm = new DefineMessage();

    final ActionElement acHome = new ActionElement();
    final ActionElement acMarkl = new ActionElement();

    final Region regHome = new Region();
    final Region regMarkl = new Region();

    final CPoint ptHome = new CPoint();
    final CPoint ptMarkl = new CPoint();

    regHome.setPoint(ptHome);
    regMarkl.setPoint(ptMarkl);

    ptHome.setX(this.home.get(number - 1).getX());
    ptHome.setY(this.home.get(number - 1).getY());

    ptMarkl.setX(this.attraction.get(number - 1).getX());
    ptMarkl.setY(this.attraction.get(number - 1).getY());

    acHome.setActionType(TypeConstants.ACTIONTYPE.HOME);
    acMarkl.setActionType(TypeConstants.ACTIONTYPE.MARKL_REGION);

    acHome.setHomeRegion(regHome);
    acMarkl.setMarklRegion(regMarkl);

    dir.addAction(acHome);
    dir.addAction(acMarkl);

    us.addInteger(number);

    dir.setUnumSet(us);
    dir.setTeamOur(false);
    dir.setDoIt(true);

    dd.setDirective(dir);
    dd.setDirectiveName("\"OPP_Form\"");

    dm.setDefineDirective(dd);

    this.olct.sendOppFormation(dm, number);

  }

  public void setCalc_length(final double calc_length) {

    this.calc_length = calc_length;
  }

  public void setEpsilonHome(final double epsilon_home) {

    this.epsilon_home = epsilon_home;
  }

  public void setEpsilonAttr(final double epsilon_attr) {

    this.epsilon_attr = epsilon_attr;
  }

  private void addPositions() {

    // System.out.println("addPositions");
    this.positions.add(new ArrayList<Vektor>(12));

    this.positions.get(this.positions.size() - 1).add(0,
        this.cwm.getBallPosition());

    // System.out.println("ball: " + positions.get(positions.size() -
    // 1).get(0));

    for (int i = 0; i < 11; i++) {
      this.positions.get(this.positions.size() - 1).add(i + 1,
          this.cwm.getPlayerPosition(i + 1, false));
      // System.out.println("player: " + (i+1) + " " +
      // positions.get(positions.size() - 1).get(i+1));
    }
  }

  private double calcErrors() {

    double error = 0;
    double error_tmp = 0;

    for (int i = 0; i < 11; i++) {

      error_tmp = 0;

      for (int j = 0; j < this.positions.size(); j++) {

        this.tmp.x = this.home.get(i).x + this.attraction.get(i).x * (this.positions
            .get(j).get(0).x - this.home.get(i).x);
        this.tmp.y = this.home.get(i).y + this.attraction.get(i).y * (this.positions
            .get(j).get(0).y - this.home.get(i).y);

        error_tmp += this.tmp.sub(this.positions.get(j).get(i + 1)).getLength();
      } // for

      error_tmp /= this.positions.size();
      // System.out.println("player " + (i+1) + ": mean_error = " + error_tmp);
      error += error_tmp;
    } // for

    error /= 11;

    // System.out.println("overall mean_error = " + error);
    return error;
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.SenseBodyInfo)
   */
  public void update(final SenseBodyInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.VisualInfo)
   */
  public void update(final VisualInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.RefereeInfo)
   */
  public void update(final RefereeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayModeInfo)
   */
  public void update(final PlayModeInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayerSayInfo)
   */
  public void update(final PlayerSayInfo info) {

    // TODO Auto-generated method stub
    
  }

  /* (non-Javadoc)
   * @see robocup.component.infotypes.InfoReceiver#update(robocup.component.infotypes.PlayerTypesInfo)
   */
  public void update(final PlayerTypesInfo info) {

    // TODO Auto-generated method stub
    
  }

}
